import Foundation
import CoreMotion
import UniformTypeIdentifiers

struct RawSample: Codable {
    let t: TimeInterval
    let ax: Double
    let ay: Double
    let az: Double
    let wx: Double
    let wy: Double
    let wz: Double
}

struct LinSample: Codable {
    let t: TimeInterval
    let ax_lin: Double
    let ay_lin: Double
    let az_lin: Double
}

final class SensorRecorder: ObservableObject {
    private let motionManager = CMMotionManager()
    private var timer: Timer?
    private(set) var rateHz: Double = 50
    
    // Buffers
    @Published private(set) var rawSamples: [RawSample] = []
    @Published private(set) var linSamples: [LinSample] = []
    
    var isRunning: Bool { timer != nil }
    
    func start(rateHz: Double = 50) {
        guard !isRunning else { return }
        self.rateHz = rateHz
        let interval = 1.0 / rateHz
        
        motionManager.accelerometerUpdateInterval = interval
        motionManager.gyroUpdateInterval = interval
        motionManager.deviceMotionUpdateInterval = interval
        
        // Start sensors
        if motionManager.isAccelerometerAvailable {
            motionManager.startAccelerometerUpdates()
        }
        if motionManager.isGyroAvailable {
            motionManager.startGyroUpdates()
        }
        if motionManager.isDeviceMotionAvailable {
            motionManager.startDeviceMotionUpdates(using: .xArbitraryCorrectedZVertical)
        }
        
        rawSamples.removeAll()
        linSamples.removeAll()
        
        // Poll at fixed 50Hz to align streams
        timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
            self?.tick()
        }
        RunLoop.current.add(timer!, forMode: .common)
    }
    
    func stop() {
        timer?.invalidate(); timer = nil
        motionManager.stopAccelerometerUpdates()
        motionManager.stopGyroUpdates()
        motionManager.stopDeviceMotionUpdates()
    }
    
    private func tick() {
        let now = Date().timeIntervalSince1970
        // Latest acc
        let acc = motionManager.accelerometerData?.acceleration
        let ax = acc?.x ?? 0
        let ay = acc?.y ?? 0
        let az = acc?.z ?? 0
        
        // Latest gyro
        let gyr = motionManager.gyroData?.rotationRate
        let wx = gyr?.x ?? 0
        let wy = gyr?.y ?? 0
        let wz = gyr?.z ?? 0
        
        rawSamples.append(RawSample(t: now, ax: ax, ay: ay, az: az, wx: wx, wy: wy, wz: wz))
        
        if let dm = motionManager.deviceMotion {
            // userAcceleration (device frame)
            let ua = dm.userAcceleration
            let R = dm.attitude.rotationMatrix
            // transform device->earth frame: world = R * device
            let axw = R.m11 * ua.x + R.m12 * ua.y + R.m13 * ua.z
            let ayw = R.m21 * ua.x + R.m22 * ua.y + R.m23 * ua.z
            let azw = R.m31 * ua.x + R.m32 * ua.y + R.m33 * ua.z
            linSamples.append(LinSample(t: now, ax_lin: axw, ay_lin: ayw, az_lin: azw))
        } else {
            linSamples.append(LinSample(t: now, ax_lin: 0, ay_lin: 0, az_lin: 0))
        }
    }
    
    // MARK: - Export CSV (Excel compatible)
    @discardableResult
    func exportCSVs(userName: String, note: String) throws -> [URL] {
        let safeName = sanitize(fileSegment: userName)
        let safeNote = sanitize(fileSegment: note.isEmpty ? "未备注" : note)
        let rawFileName = "\(safeName)_\(safeNote)_1.csv"
        let linFileName = "\(safeName)_\(safeNote)_2.csv"
        
        let rawCSV = buildRawCSV()
        let linCSV = buildLinCSV()
        
        let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let rawURL = dir.appendingPathComponent(rawFileName)
        let linURL = dir.appendingPathComponent(linFileName)
        
        try rawCSV.data(using: .utf8)?.write(to: rawURL, options: .atomic)
        try linCSV.data(using: .utf8)?.write(to: linURL, options: .atomic)
        
        return [rawURL, linURL]
    }
    
    private func buildRawCSV() -> String {
        var s = "timestamp,ax,ay,az,wx,wy,wz\n"
        for r in rawSamples {
            s += String(format: "%.6f,%.6f,%.6f,%.6f,%.6f,%.6f,%.6f\n", r.t, r.ax, r.ay, r.az, r.wx, r.wy, r.wz)
        }
        return s
    }
    
    private func buildLinCSV() -> String {
        var s = "timestamp,ax_lin,ay_lin,az_lin\n"
        for v in linSamples {
            s += String(format: "%.6f,%.6f,%.6f,%.6f\n", v.t, v.ax_lin, v.ay_lin, v.az_lin)
        }
        return s
    }
    
    private func sanitize(fileSegment: String) -> String {
        let illegal = CharacterSet(charactersIn: "/\\?%*|\"<>:")
        let cleaned = fileSegment.components(separatedBy: illegal).joined(separator: "_")
        return cleaned.replacingOccurrences(of: "\n", with: " ").replacingOccurrences(of: "\r", with: " ")
    }
}
